//
// Created by martin on 8/30/21.
//

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include "APPEaction.h"
#include "../dir.h"
#include "error.h"
#include "socket.h"
#include "cmdaction.h"

static void data_conn_event_cb(struct bufferevent *bev, short what, void *ctx);
static void data_conn_read_cb(struct bufferevent *bev, void *ctx);
static void APPE_close(Session *sess);

static void data_conn_event_cb(struct bufferevent *bev, short what, void *ctx)
{
    Session *sess = (Session *)ctx;
    int finished = 0;
    if (what & BEV_EVENT_CONNECTED) {
        printf("APPE data_conn_event_cb BEV_EVENT_CONNECTED\n");
        send_reply(sess->bev, 125, "Data connection already opened; transfer starting.");
//        bufferevent_trigger(sess->data_bev, EV_READ, 0);
    }
    else if (what & BEV_EVENT_EOF){
        struct evbuffer *input = bufferevent_get_input(sess->data_bev);
        printf("Got a close from client. %ld bytes left in the evbuffer\n", evbuffer_get_length(input));
        send_reply(sess->bev, 226, "Transfer complete.");
        finished = 1;
    }
    else if (what & BEV_EVENT_ERROR){
        printf("Got an error: %s\n", evutil_socket_error_to_string(EVUTIL_SOCKET_ERROR()));
        finished = 1;
    }
    else if (what & BEV_EVENT_TIMEOUT){
        printf("APPE data_conn_event_cb BEV_EVENT_TIMEOUT\n");
        send_reply(sess->bev, 426, "Timeout. Connection closed.");
        finished = 1;
    }

    if (finished) {
        APPE_close(sess);
    }
}

static void data_conn_read_cb(struct bufferevent *bev, void *ctx)
{
    Session *sess = (Session *)ctx;
    char data[LINE_MAX];
    size_t nread;
    ssize_t nwrite;

    do {
        nread = bufferevent_read(sess->data_bev, data, sizeof(data));
        if (nread == 0) break; /* 对端发送完毕时，发送EOF(FIN分节)，会触发BEV_EVENT_EOF事件 */

        nwrite = write(sess->recv_data_fd, data, nread);
        if (nwrite < 0) {
            err_ret("data_conn_read_cb write error");
            return;
        }
    } while (nread > 0);
}

static void APPE_close(Session *sess)
{
    printf("APPE_close\n");
    if (!sess) return;
    if (sess->data_bev) {
        bufferevent_free(sess->data_bev);
        sess->data_bev = NULL;
    }

    if (sess->recv_data_fd >= 0) {
        close(sess->recv_data_fd);
        sess->recv_data_fd = -1;
    }

    if (sess->offset > 0) {
        sess->offset = 0;
    }
}

/**
 * 追加方式写文件
 * @details 两种情况：1)如果指定文件存在，就以追加方式将数据写到文件。追加位置默认是文件末尾，如果指定了偏移offset（由前一个相邻REST命令告知），就移动至指定偏移位置。
 * 2)如果指定文件不存在，就以创建方式将数据写到文件。
 * @param sess
 * @return
 */
int APPE_action(Session *sess)
{
    if (!sess) return -1;

    // TODO 获取读锁（共享锁） -- 文件忙

    // 获取指定文件物理路径
    char file_path_phy[PATH_MAX];
    if (dir_getPhysicalPath(sess->dir, sess->cmd_text, file_path_phy, sizeof(file_path_phy)) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return -1;
    }

    // 打开文件
    int fd;
    if ((fd = open(file_path_phy, O_WRONLY | O_APPEND | O_NONBLOCK)) < 0) {
        send_reply(sess->bev, 550, "Requested action not taken; file unavailable.");
        return -1;
    }

#if 1
    // 如果有偏移offset > 0，就移动文件读写指针位置, 并且清空保存的偏移
    if (sess->offset > 0) {
        if(lseek(fd, sess->offset, SEEK_SET) < 0) {
            err_ret("lseek error");
            sess->offset = 0;
            return -1;
        }
        sess->offset = 0;
    }
#endif
    sess->recv_data_fd = fd;

    // PORT模式连接客户端
    if (connect_PORT(sess, data_conn_read_cb, NULL, data_conn_event_cb) < 0) {
        send_reply(sess->bev, 425, "Can't open data connection.");
        return -1;
    }

    return 0;
}